/*
@Time : 2020/12/29 3:16 下午
@Author : chenle
@File : cache
@Software: GoLand
*/
package lego

import (
	"encoding/json"
	"errors"
	"log"
	"strconv"
	"time"

	"github.com/gomodule/redigo/redis"
)

var RedisConn *redis.Pool

type Cache struct {
	Conn *redis.Pool
}

// Setup Initialize the Redis instance
func (l *Lego) initCache() {
	SysInfo(">>>>>>>> | redis model is setting")
	cfg := confValue.GetRedisConf()
	RedisConn = &redis.Pool{
		MaxIdle:     cfg.MaxIdle,
		MaxActive:   cfg.MaxActive,
		IdleTimeout: cfg.IdleTimeout,
		Dial: func() (redis.Conn, error) {

			c, err := redis.Dial("tcp", cfg.Host)
			if err != nil {
				return nil, err
			}
			if cfg.Password != "" {
				if _, err := c.Do("AUTH", cfg.Password); err != nil {
					c.Close()
					return nil, err
				}
			}
			return c, err
		},
		TestOnBorrow: func(c redis.Conn, t time.Time) error {
			_, err := c.Do("PING")
			return err
		},
	}
	l.Engine.Cache = &Cache{Conn: RedisConn}
	SysInfo("<<<<<<<< | init redis connection success")

	//TODO 注册键过期的回调函数，看后面的需求是否需要
	//pSubscriber.client = redis.PubSubConn{Conn: RedisConn.Get()}
	//pSubscriber.cbMap = make(map[string]PSubscribeCallback)
	//pSubscriber.Psubscribe("__keyevent@0__:expired", TestPubCallback)

	//go func() {
	//	for {
	//
	//		switch res := pSubscriber.client.Receive().(type) {
	//		case redis.Message:
	//			pattern := (*string)(unsafe.Pointer(&res.Pattern))
	//			channel := (*string)(unsafe.Pointer(&res.Channel))
	//
	//			message := (*string)(unsafe.Pointer(&res.Data))
	//			pSubscriber.cbMap[*channel](*pattern, *channel, *message)
	//		case redis.Subscription:
	//			fmt.Printf("%s: %s %d\n", res.Channel, res.Kind, res.Count)
	//		case error:
	//			ago.Log.Error(res.Error())
	//			continue
	//		}
	//	}
	//}()

}

// Set a key/value
func (c *Cache) Set(key string, data interface{}, time int) error {
	conn := c.Conn.Get()
	defer conn.Close()

	value, err := json.Marshal(data)
	if err != nil {
		return err
	}

	_, err = conn.Do("SET", key, value)
	if err != nil {
		return err
	}
	if time != 0 {
		_, err = conn.Do("EXPIRE", key, time)
		if err != nil {
			return err
		}

	}

	return nil
}
func (c *Cache) SetString(key string, value string, time int) error {
	conn := c.Conn.Get()
	defer conn.Close()
	_, err := conn.Do("SET", key, value)
	if err != nil {
		return err
	}
	if time != 0 {
		_, err = conn.Do("EXPIRE", key, time)
		if err != nil {
			return err
		}

	}
	return nil
}
func (c *Cache) GetString(key string) (string, error) {
	conn := c.Conn.Get()
	defer conn.Close()
	return redis.String(conn.Do("GET", key))
}

type PSubscribeCallback func(pattern, channel, message string)

type PSubscriber struct {
	client redis.PubSubConn
	cbMap  map[string]PSubscribeCallback
}

var pSubscriber PSubscriber

//func (c *PSubscriber) PConnect(ip string, port uint16) {
//	conn, err := redis.Dial("tcp", "127.0.0.1:6379")
//	if err != nil {
//		log.Critical("redis dial failed.")
//	}
//
//	c.client = redis.PubSubConn{conn}
//	c.cbMap = make(map[string]PSubscribeCallback)
//
//	go func() {
//		for {
//			log.Debug("wait...")
//			switch res := c.client.Receive().(type) {
//			case redis.Message:
//				pattern := (*string)(unsafe.Pointer(&res.Pattern))
//				channel := (*string)(unsafe.Pointer(&res.Channel))
//				message := (*string)(unsafe.Pointer(&res.Data))
//				c.cbMap[*channel](*pattern, *channel, *message)
//			case redis.Subscription:
//				fmt.Printf("%s: %s %d\n", res.Channel, res.Kind, res.Count)
//			case error:
//				log.Error("error handle...")
//				continue
//			}
//		}
//	}()

//}

func (c *PSubscriber) Psubscribe(channel interface{}, cb PSubscribeCallback) {
	err := c.client.PSubscribe(channel)
	if err != nil {
		log.Fatal("redis Subscribe error.")
	}

	c.cbMap[channel.(string)] = cb
}

func TestPubCallback(patter, chann, msg string) {
	log.Println("TestPubCallback patter : "+patter+" channel : ", chann, " message : ", msg)
}

func (c *Cache) SetBinary(key string, data []byte, time int) error {
	conn := c.Conn.Get()
	defer conn.Close()
	_, err := conn.Do("SET", key, data)
	if err != nil {
		return err
	}
	if time != 0 {
		_, err = conn.Do("EXPIRE", key, time)
		if err != nil {
			return err
		}

	}
	return nil
}

// Exists check a key
func (c *Cache) Exists(key string) bool {
	conn := c.Conn.Get()
	defer conn.Close()

	exists, err := redis.Bool(conn.Do("EXISTS", key))
	if err != nil {
		return false
	}

	return exists
}

func (c *Cache) Incre(key string) (int, error) {
	conn := c.Conn.Get()
	defer conn.Close()
	value, err := redis.Int(conn.Do("INCR", key))
	if err != nil {
		return 0, err
	}
	return value, nil
}

// Get get a key
func (c *Cache) Get(key string) ([]byte, error) {
	conn := c.Conn.Get()
	defer conn.Close()

	reply, err := redis.Bytes(conn.Do("GET", key))
	if err != nil {
		return nil, err
	}

	return reply, nil
}

// Delete delete a kye
func (c *Cache) Delete(key string) (bool, error) {
	conn := c.Conn.Get()
	defer conn.Close()

	return redis.Bool(conn.Do("DEL", key))
}

// LikeDeletes batch delete
func (c *Cache) LikeDeletes(key string) error {
	conn := c.Conn.Get()
	defer conn.Close()

	keys, err := redis.Strings(conn.Do("KEYS", "*"+key+"*"))
	if err != nil {
		return err
	}

	for _, key := range keys {
		_, err = c.Delete(key)
		if err != nil {
			return err
		}
	}

	return nil
}

func (c *Cache) ZAdd(key string, value, score int) error {
	conn := c.Conn.Get()
	defer conn.Close()
	_, err := conn.Do("ZADD", key, score, value)
	if err != nil {
		return err
	}
	return nil

}

func (c *Cache) ZRank(key string, value int) (int, error) {
	conn := c.Conn.Get()
	defer conn.Close()

	rank, err := redis.Int(conn.Do("ZRANK", key, strconv.Itoa(value)))
	if err != nil {
		return 0, err
	}
	return rank, nil
}

func (c *Cache) ZRange(key string, offset int) ([]int, error) {
	conn := c.Conn.Get()
	defer conn.Close()
	ranks, err := redis.Ints(conn.Do("ZRANGE", key, 0, offset))
	if err != nil {
		return nil, err
	}

	return ranks, nil
}

// 增加计数
func (c *Cache) INCRBY(key string, increment int) (int, error) {
	conn := c.Conn.Get()
	defer conn.Close()
	newNum, err := redis.Int(conn.Do("INCRBY", key, increment))
	if err != nil {
		return 0, err
	}
	return newNum, nil
}

// 获取计数
func (c *Cache) GetCounter(key string) (int, error) {
	conn := c.Conn.Get()
	defer conn.Close()
	count, err := redis.Int(conn.Do("GET", key))
	if err != nil {
		return 0, err
	}
	return count, nil
}

// 初始化计数器
func (c *Cache) InitCounter(key string) error {
	conn := c.Conn.Get()
	defer conn.Close()
	_, err := conn.Do("SET", key, 0)
	if err != nil {
		return err
	}
	return nil
}

var ErrCounterHasBeenZero = errors.New("the counter has been 0")

// 减少计数
func (c *Cache) DECRBY(key string, decrement int) (int, error) {
	conn := c.Conn.Get()
	defer conn.Close()
	count, err := c.GetCounter(key)
	if err != nil {
		return 0, err
	}
	if count == 0 {
		return 0, ErrCounterHasBeenZero
	}
	newNum, err := redis.Int(conn.Do("DECRBY", key, decrement))
	if err != nil {
		return 0, err
	}
	return newNum, nil
}
